﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace CAP.Forms
{
    public partial class FloodSetAlgorithmState : UserControl
    {
        #region Konstanten
        private const string givenGraphDescription = "Graphdefinition:";
        private const string givenGraphDescription1 = "N={ alle Knoten }, V={ alle möglichen Werte }, Entscheidung d ∈ V ∪{ unbekannt }, Menge W ⊆ V.";
        private const string initialisationCode = "Initialisierung: Für jedes i∈N: W = { Die Singleton Menge des Initialwerts }";
        private const string roundR = "Runde r mit 1<f+1:";
        private const string roundRBroadCastCode = "Broadcastphase: i∈N sendet W an j∈N.";
        private const string roundRRecieveCode = "Empfangsphase: i∈N erhält Nachricht x von j∈N -> W = W ∪ {Vereinigungsmenge aller erhaltenen Werte}.";
        private const string roundGreaterThanF = "Runde f+1:";
        private const string decisionCode = "Entscheidungsphase: Wenn |W| = 1  dann d=v, wobei W={v}, falls mehr als ein Wert, dann d=v0.";




        #endregion Konstanten

        #region Attribute
        private DistributedGraph.FloodSet.AlgorithmController algorithm;
        private Font mathFont = new Font("Cambria", 8F, FontStyle.Regular, GraphicsUnit.Point);
        private Font arrowFont = new Font("Wingdings 3", 8F, FontStyle.Bold, GraphicsUnit.Point);
        private float standardHeight = -1;
        private float standardWidth = -1;
        private Size sizeDiff;
        #endregion Attribute

        #region Eigenschaften
        /// <summary>
        /// Setzt den AlgorithmusController für das Control.
        /// </summary>
        public DistributedGraph.FloodSet.AlgorithmController Algorithm
        {
            set
            {
                algorithm = value;

                algorithm.ProcessStarted += new DistributedGraph.ProgressAlgorithmEvent(LogProcessStart);
                algorithm.RoundFinished += new DistributedGraph.ProgressAlgorithmEvent(LogRoundFinished);
                foreach (DistributedGraph.FloodSet.Node node in algorithm.Nodes)
                {
                    node.MessageSent += new DistributedGraph.FloodSet.Node.MsgEvent(LogMsgSent);
                    node.MessageReceived += new DistributedGraph.FloodSet.Node.MsgEvent(LogMsgRecieved);
                    node.NoMessageReceived += new DistributedGraph.FloodSet.Node.MsgEvent(LogNoMsgRecieved);
                    node.RoundFinished += new DistributedGraph.FloodSet.Node.SimpleEvent(LogRoundFinished);
                    node.DecisionFinished += new DistributedGraph.FloodSet.Node.SimpleEvent(LogDecisionFinished);
                    node.Stopping += new DistributedGraph.Node.SimpleEvent(LogNodeStopping);

                }
                rtbChronic.Text = "";
            }
        }
        #endregion Eigenschaften

        #region Methoden
        /// <summary>
        /// Instanziert und initialisiert das Control.
        /// </summary>
        public FloodSetAlgorithmState()
        {
            InitializeComponent();
            this.ClientSizeChanged += new EventHandler(ResizeTextbox);
        }
        /// <summary>
        /// Setzt die Werte für standardHeight und standardWidth basierend auf der Größe des algrithmText Controls.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            if (standardHeight <= 0)
            {
                if (standardHeight == -1) sizeDiff = new Size(this.Size.Width - algorithmTextBox.Size.Width, this.Size.Height - algorithmTextBox.Size.Height);
                standardHeight = standardHeight == -1 ? standardHeight = 0 : this.Size.Height - sizeDiff.Height;
                standardWidth = standardWidth == -1 ? standardWidth = 0 : this.Size.Width - sizeDiff.Width;
            }
        }

        /// <summary>
        /// Passt dynamisch die Textgröße an die Fenstergröße des algrithmText Controls an.
        /// </summary>
        /// <param name="o"></param>
        /// <param name="a"></param>
        private void ResizeTextbox(object o, EventArgs a)
        {
            float fontSize = Math.Max(8.0f, Math.Min(8.0f * this.Size.Width / standardWidth * this.Size.Height / standardHeight, 14));
            mathFont = new Font("Cambria", fontSize, FontStyle.Regular, GraphicsUnit.Point);
            arrowFont = new Font("Wingdings 3", fontSize, FontStyle.Bold, GraphicsUnit.Point);
            Update();
        }

        /// <summary>
        /// Aktualisiert das Control unter Zuhilfenahme des Algorithmuskontrollers.
        /// </summary>
        public new void Update()
        {
            base.Update();

            if (algorithm != null)
            {
             
                System.Windows.Forms.RichTextBox bufferTextBox = new System.Windows.Forms.RichTextBox();

                //Fülle den Text in die Textbox
                bufferTextBox.Text = givenGraphDescription + "\n" + givenGraphDescription1 + "\n\n" + initialisationCode + "\n\n" + roundR + "\n" + roundRBroadCastCode + "\n" + roundRRecieveCode + "\n\n" + roundGreaterThanF + "\n" + decisionCode;


                //Setze Schriftart
                bufferTextBox.SelectionStart = 0;

                Padding before = bufferTextBox.Margin;
                bufferTextBox.SelectionLength = bufferTextBox.Text.Length;
                bufferTextBox.SelectionFont = mathFont;


                // Hebe aktuelle Phase im Text hervor
                if (algorithm.Round == 0 && algorithm.CurrentProcess == 0)
                {
                    pasteMarker(bufferTextBox, markText(bufferTextBox, initialisationCode));
                }
                else if (algorithm.Round < algorithm.MaxRounds)
                {
                    pasteMarker(bufferTextBox, markText(bufferTextBox, roundR));
                    pasteMarker(bufferTextBox, markText(bufferTextBox, roundRBroadCastCode));
                    pasteMarker(bufferTextBox, markText(bufferTextBox, roundRRecieveCode));
                }
                else if (algorithm.Round == algorithm.MaxRounds)
                {
                    pasteMarker(bufferTextBox, markText(bufferTextBox, roundGreaterThanF));
                    pasteMarker(bufferTextBox, markText(bufferTextBox, decisionCode));
                }


                algorithmTextBox.Rtf = bufferTextBox.Rtf;

            }
        }
        // <summary>
        /// Hebt eine Textstelle in der formalen Algorithmusdefinition hervor.
        /// </summary>
        /// <param name="textBox">Die RichtTextbox in der der Text hervorgehoben werden soll.</param>
        /// <param name="mystring">Der String der hervorgehoben werden soll.</param>
        /// <returns>Position des ersten Characters des Strings.</returns>
        private int markText(System.Windows.Forms.RichTextBox textBox, string mystring)
        {
            int my1stPosition = -1;
            my1stPosition = textBox.Find(mystring);
            if (my1stPosition >= 0)
            {


                textBox.SelectionStart = my1stPosition;
                textBox.SelectionLength = mystring.Length;
                textBox.SelectionColor = Color.Red;


            }
            return my1stPosition;
        }


        /// <summary>
        /// Setzt einen Pfeilcharacter aus dem Font "Wingdings 3" an die gegebene Position in die Textbox.
        /// </summary>
        /// <param name="textBox">Die RichtTextbox in der der Pfeilcharacter gesetzt werden soll.</param>
        /// <param name="position">Position im String der gesamten Textbox an der der Pfeilcharacter gesetzt werden soll</param>
        private void pasteMarker(System.Windows.Forms.RichTextBox textBox, int position)
        {

            if (position >= 0)
            {
                textBox.SelectionStart = position;

                textBox.SelectionLength = 0;
                textBox.SelectedText = " ";// Pfeil nach Rechts in Font Wingdings 3
                textBox.SelectionLength = 2;
                textBox.SelectionStart = position - 1;
                textBox.SelectionFont = arrowFont;
            }


        }
        /// <summary>
        ///  Fügt Text in das Logging Control an und Scrollt an die letzte Position
        /// </summary>
        /// <param name="text">Der String der an die Logging Control angehängt werden soll</param>
        private void AppendText(string text)
        {
            rtbChronic.AppendText((rtbChronic.Text.Length == 0 ? "" : "\n") + text);
            rtbChronic.SelectionStart = rtbChronic.Text.Length;
            rtbChronic.ScrollToCaret();
        }



        /// <summary>
        /// Wird vom MessageSent-EventHandler eines Nodes aufgerufen und gibt in den Log Controller aus, dass eine Nachricht von einem Node gesendet wurde. 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="receiver"></param>
        /// <param name="link"></param>
        /// <param name="message"></param>
        private void LogMsgSent(DistributedGraph.FloodSet.Node sender, DistributedGraph.FloodSet.Node receiver, DistributedGraph.Link link, DistributedGraph.FloodSet.Message message)
        {
            AppendText("\tNachricht an Prozess " + receiver.Id + " gesendet");
        }

        /// <summary>
        /// Wird vom MessageReceived-EventHandler eines Nodes aufgerufen und gibt in den Log Controller aus, dass eine Nachricht an einem Node angekommen ist. 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="receiver"></param>
        /// <param name="link"></param>
        /// <param name="message"></param>
        private void LogMsgRecieved(DistributedGraph.Node sender, DistributedGraph.Node receiver, DistributedGraph.Link link, DistributedGraph.IMessage message)
        {
            AppendText("\tProzess " + receiver.Id + " hat die Nachricht erhalten");
        }

        /// <summary>
        ///  Wird vom NoMessageReceived-EventHandler eines Nodes aufgerufen und gibt in den Log Controller aus, dass es zu einem Time-Out kam, d.h. dass eine Nachricht an einem Node innerhalb einer Runde nicht ankam.
        ///  </summary>
        /// <param name="sender"></param>
        /// <param name="receiver"></param>
        /// <param name="link"></param>
        /// <param name="message"></param>
        private void LogNoMsgRecieved(DistributedGraph.Node sender, DistributedGraph.Node receiver, DistributedGraph.Link link, DistributedGraph.IMessage message)
        {
            AppendText("\tTimeout - Prozess " + receiver.Id + " hat keine Nachricht von Prozess " + sender.Id + " erhalten");
        }

        /// <summary>
        /// Wird vom RoundFinished-EventHandler eines Nodes aufgerufen und gibt in den Log Controller aus, dass ein Node alle Aktionen (Senden und Empfangen von Nachrichten) innerhalb der aktuellen Runde ausgeführt hat und die Runde für diesen Node beendet ist.
        /// </summary>
        /// <param name="node">Der Node der das Event geworfen hat.</param>
        private void LogRoundFinished(DistributedGraph.FloodSet.Node node)
        {
            AppendText("   Prozess " + node.Id + " hat die Runde abgeschlossen.");
        }

        /// <summary>
        /// Wird vom DecisionFinished-EventHandler eines Nodes aufgerufen und gibt in den Log Controller aus, dass ein Node die Entscheidungsphase beendet hat.
        /// </summary>
        /// <param name="node">Der Node der das Event geworfen hat.</param>
        private void LogDecisionFinished(DistributedGraph.FloodSet.Node node)
        {
            AppendText("   Prozess " + node.Id + " hat die Entscheidungsfindung abgeschlossen.");
        }



        /// <summary>
        /// Wird vom ProcessStarted-EventHandler des FloodSet-Algorithmuscontrollers aufgerufen und gibt in den Log Controller aus, dass ein Prozess entsprechend zu einem Knoten im Graph gestartet wurde.
        /// </summary>
        /// <param name="fromRound"></param>
        /// <param name="toRound"></param>
        /// <param name="process"></param>
        void LogProcessStart(int fromRound, int toRound, int process)
        {
            if (fromRound != toRound || (toRound == 0 && process == 0)) AppendText("Runde " + (toRound + 1).ToString() + ":");
            AppendText("   Prozess  " + (process + 1).ToString() + ":");
        }

        /// <summary>
        /// Wird vom RoundFinished-EventHandler des FloodSet-Algorithmuscontrollers aufgerufen und gibt in den Log Controller aus, dass der Algorithmus abgeschlossen und Konsens gefunden wurde.
        /// </summary>
        /// <param name="fromRound"></param>
        /// <param name="toRound"></param>
        /// <param name="process"></param>
        void LogRoundFinished(int fromRound, int toRound, int process)
        {
            if (toRound == algorithm.MaxRounds) AppendText("Konsensfindung abgeschlossen.");
        }


        /// <summary>
        /// Wird vom Stopping-Eventhandler eines Nodes aufgerufen und gibt im Log Controller aus, dass ein Node vom Benutzer als ausgefallen markiert wurde.
        /// </summary>
        /// <param name="node">Der Node der das Event geworfen hat.</param>
        void LogNodeStopping(DistributedGraph.Node node)
        {
            AppendText("   Prozess " + node.Id.ToString() + " ist ausgefallen!");
        }

        #endregion Methoden
    }
}
